﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Options;

namespace Despatching
{
    public class Despatcher<T> where T : IEquatable<T>
    {
        /// <summary>等待执行</summary>
        readonly Queue<T> _waitings = new();
        /// <summary>正在执行</summary>
        readonly LinkedList<(T id, DateTimeOffset expireTime)> _executings = new();
        readonly SemaphoreSlim _lockForWaitings = new(1);
        readonly SemaphoreSlim _lockForExecutings = new(1);
        readonly CountdownEvent _lockWhenWriteData = new(0);
        readonly CountdownEvent _lockWhenReadData = new(0);
        readonly DespatcherOptions _options;

        public Despatcher(IOptions<DespatcherOptions> options)
        {
            _options = options.Value;
        }

        /// <summary>将正在执行中已超时的放回到待执行</summary>
        private void ReturnExpired()
        {
            var now = DateTimeOffset.Now;
            for(var node = _executings.First; node != null;)
            {
                var n = node;
                node = node.Next;

                if(n.Value.expireTime < now)
                {
                    _waitings.Enqueue(n.Value.id);
                    _executings.Remove(n);
                }
            }
        }

        /// <summary>获取未执行</summary>
        /// <param name="getData">从数据源获取数据</param>
        /// <returns></returns>
        private async Task ReloadingData(Func<IEnumerable<T>, Task<ICollection<T>>> getData)
        {
            await _lockForExecutings.WaitAsync();
            try
            {
                _lockWhenWriteData.Wait();
                _lockWhenReadData.TryAddCountOrReset();
                ICollection<T> data;
                try
                {
                    data = await getData(_executings.Select(it => it.id));
                }
                finally
                {
                    _lockWhenReadData.Signal();
                }
                ReturnExpired();
                foreach(var i in data)
                {
                    _waitings.Enqueue(i);
                }
            }
            finally
            {
                _lockForExecutings.Release();
            }
        }

        /// <summary>获取一个要执行的id</summary>
        /// <param name="getData">从数据源填装数据</param>
        /// <returns></returns>
        internal async Task<(bool, T?)> TryGetAsync(Func<IEnumerable<T>, Task<ICollection<T>>> getData)
        {
            await _lockForWaitings.WaitAsync();
            try
            {
                // 带执行取完
                if(!_waitings.Any())
                {
                    await ReloadingData(getData);
                }

                if(!_waitings.Any())
                    return (false, default);

                // 给出一个待执行
                var id = _waitings.Dequeue();

                // 放入正在执行
                await _lockForExecutings.WaitAsync();
                _executings.AddLast((id, DateTimeOffset.Now.Add(_options.ExpireIn)));
                _lockForExecutings.Release();

                return (true, id);
            }
            finally
            {
                _lockForWaitings.Release();
            }
        }

        /// <summary>反馈执行完毕</summary>
        /// <param name="id"></param>
        /// <param name="saveData">保存执行完的数据</param>
        public async Task<bool> BackAsync(T id, Func<T, Task> saveData)
        {
            await _lockForExecutings.WaitAsync();
            bool isFound = _executings.Remove(it => it.id.Equals(id));
            _lockForExecutings.Release();

            if(isFound)
            {
                _lockWhenReadData.Wait();
                _lockWhenWriteData.TryAddCountOrReset();
                try
                {
                    await saveData(id);
                }
                catch
                {
                    _executings.AddLast((id, DateTimeOffset.MinValue));
                    throw;
                }
                finally
                {
                    _lockWhenWriteData.Signal();
                }
            }

            return isFound;
        }

    }

    public static class DespatcherClassExtensions
    {
        public static async Task<T?> GetAsync<T>(this Despatcher<T> @this, Func<IEnumerable<T>, Task<ICollection<T>>> getData) where T : class, IEquatable<T>
        {
            var (_, id) = await @this.TryGetAsync(getData);
            return id;
        }
    }

    public static class DespatcherStructExtensions
    {
        public static async Task<T?> GetAsync<T>(this Despatcher<T> @this, Func<IEnumerable<T>, Task<ICollection<T>>> getData) where T : struct, IEquatable<T>
        {
            var (succes, id) = await @this.TryGetAsync(getData);
            return succes ? id : null;
        }
    }
}
